﻿using System;
using System.Collections.Generic;
using BasicFramework.Rendering.Primitives;
using System.Runtime.InteropServices;

namespace BasicFramework.Rendering.Buffer
{
    public abstract class DynamicPrimitiveBuffer<T> : IDisposable where T : struct,IPrimitive
    {
        #region Fields

        internal const int InitialBufferSize = 32;

        private List<T> primitives;
        private int bufferSize;
        private bool needsCommit;

        #endregion


        #region Properties

        /// <summary>
        /// Gets the number of Primitives in the Buffer
        /// </summary>
        public int Count
        {
            get { return this.Primitives.Count; }
        }

        /// <summary>
        /// Gets the size in Byte of a single Buffer Primitive
        /// </summary>
        public int ElementSize
        {
            get { return Marshal.SizeOf(typeof(T)); }
        }

        /// <summary>
        /// Gets or sets the size in bytes of the Buffer
        /// </summary>
        internal int BufferSize
        {
            get { return bufferSize; }
            set { this.bufferSize = value; }
        }

        public List<T> Primitives
        {
            get { return primitives; }
            set { this.primitives = value; }
        }

        #endregion


        #region Constructors

        public DynamicPrimitiveBuffer()
        {
            this.primitives = new List<T>();
        }

        ~DynamicPrimitiveBuffer()
        {
            this.Dispose();
            GC.SuppressFinalize(this);
        }



        #endregion


        #region Events

        #endregion


        #region Private methods

        internal abstract void FillBuffer();
        internal abstract void SetPrimitiveDeclaration();
        internal abstract void SetBufferInternal(int stream);
        internal abstract void ResizeBuffer(int newSize);
        internal abstract void DisposeBuffer();

        #endregion


        #region Public methods

        /// <summary>
        /// Adds a primitive to the Buffer
        /// </summary>
        public void Add(T primitive)
        {
            this.Primitives.Add(primitive);
            if (this.Primitives.Count > this.BufferSize)
            {
                this.BufferSize = this.BufferSize == 0 ? InitialBufferSize : this.BufferSize * 2;
                this.ResizeBuffer(this.BufferSize * this.ElementSize);
            }
            this.needsCommit = true;
        }

        /// <summary>
        /// Adds a primitive to the Buffer
        /// </summary>
        public void Add(List<T> primitives)
        {
            this.Primitives.AddRange(primitives);
            if (this.Primitives.Count > this.BufferSize)
            {
                this.BufferSize = this.BufferSize == 0 ? InitialBufferSize : this.BufferSize * 2;
                this.ResizeBuffer(this.BufferSize * this.ElementSize);
            }
            this.needsCommit = true;
        }

        /// <summary>
        /// Adds a primitive to the Buffer
        /// </summary>
        public void Add(T[] primitives)
        {
            this.Primitives.AddRange(primitives);
            if (this.Primitives.Count > this.BufferSize)
            {
                this.BufferSize = this.BufferSize == 0 ? InitialBufferSize : this.BufferSize * 2;
                this.ResizeBuffer(this.BufferSize * this.ElementSize);
            }
            this.needsCommit = true;

        }

        /// <summary>
        /// Clears all Primitives of the Buffer
        /// </summary>
        public void Clear()
        {
            this.Primitives.Clear();
            this.needsCommit = true;
        }

        /// <summary>
        /// Commit all buffer changes.
        /// </summary>
        public void Commit()
        {
            if (this.needsCommit)
            {
                this.FillBuffer();
                this.SetPrimitiveDeclaration();
                this.needsCommit = false;
            }
        }

        /// <summary>
        /// Sets the Buffer to the Device in the specified Stream
        /// </summary>
        /// <param name="stream">The number of the Stream</param>
        public void SetBuffer(int stream)
        {
            this.SetBufferInternal(stream);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this.DisposeBuffer();
        }

        #endregion

    }
}
